#include <fstream>
#include "md2model.h"
#include "../imageLoader.h"
#include "../error.h"
#include "../loadingFunctions.h"
#include "../handyFunctions.h"

using namespace std;

namespace {
	//Normals used in the MD2 file format
	float NORMALS[486] =
		{-0.525731f,  0.000000f,  0.850651f,
		 -0.442863f,  0.238856f,  0.864188f,
		 -0.295242f,  0.000000f,  0.955423f,
		 -0.309017f,  0.500000f,  0.809017f,
		 -0.162460f,  0.262866f,  0.951056f,
		  0.000000f,  0.000000f,  1.000000f,
		  0.000000f,  0.850651f,  0.525731f,
		 -0.147621f,  0.716567f,  0.681718f,
		  0.147621f,  0.716567f,  0.681718f,
		  0.000000f,  0.525731f,  0.850651f,
		  0.309017f,  0.500000f,  0.809017f,
		  0.525731f,  0.000000f,  0.850651f,
		  0.295242f,  0.000000f,  0.955423f,
		  0.442863f,  0.238856f,  0.864188f,
		  0.162460f,  0.262866f,  0.951056f,
		 -0.681718f,  0.147621f,  0.716567f,
		 -0.809017f,  0.309017f,  0.500000f,
		 -0.587785f,  0.425325f,  0.688191f,
		 -0.850651f,  0.525731f,  0.000000f,
		 -0.864188f,  0.442863f,  0.238856f,
		 -0.716567f,  0.681718f,  0.147621f,
		 -0.688191f,  0.587785f,  0.425325f,
		 -0.500000f,  0.809017f,  0.309017f,
		 -0.238856f,  0.864188f,  0.442863f,
		 -0.425325f,  0.688191f,  0.587785f,
		 -0.716567f,  0.681718f, -0.147621f,
		 -0.500000f,  0.809017f, -0.309017f,
		 -0.525731f,  0.850651f,  0.000000f,
		  0.000000f,  0.850651f, -0.525731f,
		 -0.238856f,  0.864188f, -0.442863f,
		  0.000000f,  0.955423f, -0.295242f,
		 -0.262866f,  0.951056f, -0.162460f,
		  0.000000f,  1.000000f,  0.000000f,
		  0.000000f,  0.955423f,  0.295242f,
		 -0.262866f,  0.951056f,  0.162460f,
		  0.238856f,  0.864188f,  0.442863f,
		  0.262866f,  0.951056f,  0.162460f,
		  0.500000f,  0.809017f,  0.309017f,
		  0.238856f,  0.864188f, -0.442863f,
		  0.262866f,  0.951056f, -0.162460f,
		  0.500000f,  0.809017f, -0.309017f,
		  0.850651f,  0.525731f,  0.000000f,
		  0.716567f,  0.681718f,  0.147621f,
		  0.716567f,  0.681718f, -0.147621f,
		  0.525731f,  0.850651f,  0.000000f,
		  0.425325f,  0.688191f,  0.587785f,
		  0.864188f,  0.442863f,  0.238856f,
		  0.688191f,  0.587785f,  0.425325f,
		  0.809017f,  0.309017f,  0.500000f,
		  0.681718f,  0.147621f,  0.716567f,
		  0.587785f,  0.425325f,  0.688191f,
		  0.955423f,  0.295242f,  0.000000f,
		  1.000000f,  0.000000f,  0.000000f,
		  0.951056f,  0.162460f,  0.262866f,
		  0.850651f, -0.525731f,  0.000000f,
		  0.955423f, -0.295242f,  0.000000f,
		  0.864188f, -0.442863f,  0.238856f,
		  0.951056f, -0.162460f,  0.262866f,
		  0.809017f, -0.309017f,  0.500000f,
		  0.681718f, -0.147621f,  0.716567f,
		  0.850651f,  0.000000f,  0.525731f,
		  0.864188f,  0.442863f, -0.238856f,
		  0.809017f,  0.309017f, -0.500000f,
		  0.951056f,  0.162460f, -0.262866f,
		  0.525731f,  0.000000f, -0.850651f,
		  0.681718f,  0.147621f, -0.716567f,
		  0.681718f, -0.147621f, -0.716567f,
		  0.850651f,  0.000000f, -0.525731f,
		  0.809017f, -0.309017f, -0.500000f,
		  0.864188f, -0.442863f, -0.238856f,
		  0.951056f, -0.162460f, -0.262866f,
		  0.147621f,  0.716567f, -0.681718f,
		  0.309017f,  0.500000f, -0.809017f,
		  0.425325f,  0.688191f, -0.587785f,
		  0.442863f,  0.238856f, -0.864188f,
		  0.587785f,  0.425325f, -0.688191f,
		  0.688191f,  0.587785f, -0.425325f,
		 -0.147621f,  0.716567f, -0.681718f,
		 -0.309017f,  0.500000f, -0.809017f,
		  0.000000f,  0.525731f, -0.850651f,
		 -0.525731f,  0.000000f, -0.850651f,
		 -0.442863f,  0.238856f, -0.864188f,
		 -0.295242f,  0.000000f, -0.955423f,
		 -0.162460f,  0.262866f, -0.951056f,
		  0.000000f,  0.000000f, -1.000000f,
		  0.295242f,  0.000000f, -0.955423f,
		  0.162460f,  0.262866f, -0.951056f,
		 -0.442863f, -0.238856f, -0.864188f,
		 -0.309017f, -0.500000f, -0.809017f,
		 -0.162460f, -0.262866f, -0.951056f,
		  0.000000f, -0.850651f, -0.525731f,
		 -0.147621f, -0.716567f, -0.681718f,
		  0.147621f, -0.716567f, -0.681718f,
		  0.000000f, -0.525731f, -0.850651f,
		  0.309017f, -0.500000f, -0.809017f,
		  0.442863f, -0.238856f, -0.864188f,
		  0.162460f, -0.262866f, -0.951056f,
		  0.238856f, -0.864188f, -0.442863f,
		  0.500000f, -0.809017f, -0.309017f,
		  0.425325f, -0.688191f, -0.587785f,
		  0.716567f, -0.681718f, -0.147621f,
		  0.688191f, -0.587785f, -0.425325f,
		  0.587785f, -0.425325f, -0.688191f,
		  0.000000f, -0.955423f, -0.295242f,
		  0.000000f, -1.000000f,  0.000000f,
		  0.262866f, -0.951056f, -0.162460f,
		  0.000000f, -0.850651f,  0.525731f,
		  0.000000f, -0.955423f,  0.295242f,
		  0.238856f, -0.864188f,  0.442863f,
		  0.262866f, -0.951056f,  0.162460f,
		  0.500000f, -0.809017f,  0.309017f,
		  0.716567f, -0.681718f,  0.147621f,
		  0.525731f, -0.850651f,  0.000000f,
		 -0.238856f, -0.864188f, -0.442863f,
		 -0.500000f, -0.809017f, -0.309017f,
		 -0.262866f, -0.951056f, -0.162460f,
		 -0.850651f, -0.525731f,  0.000000f,
		 -0.716567f, -0.681718f, -0.147621f,
		 -0.716567f, -0.681718f,  0.147621f,
		 -0.525731f, -0.850651f,  0.000000f,
		 -0.500000f, -0.809017f,  0.309017f,
		 -0.238856f, -0.864188f,  0.442863f,
		 -0.262866f, -0.951056f,  0.162460f,
		 -0.864188f, -0.442863f,  0.238856f,
		 -0.809017f, -0.309017f,  0.500000f,
		 -0.688191f, -0.587785f,  0.425325f,
		 -0.681718f, -0.147621f,  0.716567f,
		 -0.442863f, -0.238856f,  0.864188f,
		 -0.587785f, -0.425325f,  0.688191f,
		 -0.309017f, -0.500000f,  0.809017f,
		 -0.147621f, -0.716567f,  0.681718f,
		 -0.425325f, -0.688191f,  0.587785f,
		 -0.162460f, -0.262866f,  0.951056f,
		  0.442863f, -0.238856f,  0.864188f,
		  0.162460f, -0.262866f,  0.951056f,
		  0.309017f, -0.500000f,  0.809017f,
		  0.147621f, -0.716567f,  0.681718f,
		  0.000000f, -0.525731f,  0.850651f,
		  0.425325f, -0.688191f,  0.587785f,
		  0.587785f, -0.425325f,  0.688191f,
		  0.688191f, -0.587785f,  0.425325f,
		 -0.955423f,  0.295242f,  0.000000f,
		 -0.951056f,  0.162460f,  0.262866f,
		 -1.000000f,  0.000000f,  0.000000f,
		 -0.850651f,  0.000000f,  0.525731f,
		 -0.955423f, -0.295242f,  0.000000f,
		 -0.951056f, -0.162460f,  0.262866f,
		 -0.864188f,  0.442863f, -0.238856f,
		 -0.951056f,  0.162460f, -0.262866f,
		 -0.809017f,  0.309017f, -0.500000f,
		 -0.864188f, -0.442863f, -0.238856f,
		 -0.951056f, -0.162460f, -0.262866f,
		 -0.809017f, -0.309017f, -0.500000f,
		 -0.681718f,  0.147621f, -0.716567f,
		 -0.681718f, -0.147621f, -0.716567f,
		 -0.850651f,  0.000000f, -0.525731f,
		 -0.688191f,  0.587785f, -0.425325f,
		 -0.587785f,  0.425325f, -0.688191f,
		 -0.425325f,  0.688191f, -0.587785f,
		 -0.425325f, -0.688191f, -0.587785f,
		 -0.587785f, -0.425325f, -0.688191f,
		 -0.688191f, -0.587785f, -0.425325f};
	
	
}

MD2Model::~MD2Model()
{
	if (frames != NULL)
	{
		for(int i = 0; i < numFrames; i++)
		{
			delete[] frames[i].vertices;
		}
		delete[] frames;
	}
	
	if (texCoords != NULL)
	{
		delete[] texCoords;
	}
	if (triangles != NULL)
	{
		delete[] triangles;
	}
}

MD2Model::MD2Model()
{
	frames = NULL;
	texCoords = NULL;
	triangles = NULL;
	textureId = NULL;
	time = 0;
	isDead = false;
	dies = false;
	returnToPreviousFrame = false;
	life = 3;
	finishedAnimation = false;
}

//Loads the MD2 model
MD2Model* MD2Model::load(const char* filename)
{
	printf("Loading model: %s...\n", filename);
	ifstream input;
	input.open(filename, istream::binary);

	//file not found
	if (input.fail())
	{
		Error::printError(MODEL_NOT_FOUND, filename);
		return NULL;
	}

	//table for storing errors
	char error[100];
	memset(error, '\0', 100);
	
	char buffer[64];
	input.read(buffer, 4); //Should be "IPD2", if this is an MD2 file
	if (buffer[0] != 'I' || buffer[1] != 'D' ||
		buffer[2] != 'P' || buffer[3] != '2')
	{
		Error::printError(NOT_MD2_FILE, filename);
		return NULL;
	}

	if (readInt(input) != 8)
	{ //The version number
		strcat_s(error, "The version of the md2 file is not 8. Unfortunately I can't process such files: ");
		strcat_s(error, filename);
		Error::printError(OWN_ERROR, error);
		return NULL;
	}
	
	int textureWidth = readInt(input);   //The width of the textures
	int textureHeight = readInt(input);  //The height of the textures
	readInt(input);                      //The number of bytes per frame
	int numTextures = readInt(input);    //The number of textures
	if (numTextures > 1)
	{
		strcat_s(error, "The number of textures in the md2 file is not equal to 1: ");
		strcat_s(error, filename);
		Error::printError(OWN_ERROR, error);
		return NULL;
	}
	int numVertices = readInt(input);    //The number of vertices
	int numTexCoords = readInt(input);   //The number of texture coordinates
	int numTriangles = readInt(input);   //The number of triangles
	readInt(input);                      //The number of OpenGL commands
	int numFrames = readInt(input);      //The number of frames
	
	//Offsets (number of bytes after the beginning of the file to the beginning
	//of where certain data appear)
	int textureOffset = readInt(input);  //The offset to the textures
	int texCoordOffset = readInt(input); //The offset to the texture coordinates
	int triangleOffset = readInt(input); //The offset to the triangles
	int frameOffset = readInt(input);    //The offset to the frames
	readInt(input);                      //The offset to the OpenGL commands
	readInt(input);                      //The offset to the end of the file

	MD2Model* model = new MD2Model();

	if (!(numTextures > 0))
		Error::printError(OWN_ERROR, "Number of textures in the md2 model is not greater than 0. Continuing happily.");
	
	//Load the texture
	//printf("Preparing to load the model texture\n");

	input.seekg(textureOffset, ios_base::beg);
	input.read(buffer, 64);

	char textureExtension[5];
	strcpy_s(textureExtension, buffer + strlen(buffer) - 4);

	if (strlen(buffer) < 5 ||
		(strcmp(textureExtension, ".bmp") != 0 && strcmp(textureExtension, ".pcx") != 0))
	{
		strcpy_s(textureExtension, ".pcx");
		Error::printError(OWN_ERROR, "The texture extension couldn't be decoded. Trying .pcx...");
	}
	
	Image* image = NULL;
	char textureName[64];
	strcpy_s(textureName, filename);
	//get just the name of the model (remove the ".md2" extension)
	textureName[strlen(textureName)-4] = '\0';
	strcat_s(textureName, textureExtension);

	char fileName[64];
	char filePath[64];
	char texturePath[128] = "";
	char textures[] = "textures/";
	char pcx[] = ".pcx";
	char bmp[] = ".bmp";

	//get the name of the file and its path
	explodeName(filename, fileName, filePath);
	//craete the path to the texture... At first - add previous path
	strcpy_s(texturePath, filePath);
	//then add the name of the textures folder ("textures")
	texturePath[strlen(texturePath)] = '/';
	strcat_s(texturePath, textures);
	//then add the name of the file
	strcat_s(texturePath, fileName);

	if (strcmp(textureExtension, ".bmp") == 0)
	{
		//at the end add the file extension. It could be .bmp...
		strcat_s(texturePath, bmp);
		image = loadBMP(texturePath);
	}
	else if (strcmp(textureExtension, ".pcx") == 0)
	{
		//...or .pcx
		strcat_s(texturePath, pcx);
		image = loadPCX(texturePath);
	}

	//couldn't load the model texture
	if (image == NULL)
	{
		Error::printError(MODEL_TEXTURE_NOT_LOADED, filename);
		return NULL;
	}

	GLuint textureId = loadTexture(image);
	model->textureId = textureId;
	
	
	//Load the texture coordinates
	input.seekg(texCoordOffset, ios_base::beg);
	model->texCoords = new MD2TexCoord[numTexCoords];
	for(int i = 0; i < numTexCoords; i++)
	{
		MD2TexCoord* texCoord = model->texCoords + i;
		texCoord->texCoordX = (float)readShort(input) / textureWidth;
		texCoord->texCoordY = 1 - (float)readShort(input) / textureHeight;
	}
	
	//Load the triangles
	input.seekg(triangleOffset, ios_base::beg);
	model->triangles = new MD2Triangle[numTriangles];
	model->numTriangles = numTriangles;
	for(int i = 0; i < numTriangles; i++)
	{
		MD2Triangle* triangle = model->triangles + i;
		for(int j = 0; j < 3; j++)
		{
			triangle->vertices[j] = readUShort(input);
		}
		for(int j = 0; j < 3; j++)
		{
			triangle->texCoords[j] = readUShort(input);
		}
	}
	
	//Load the frames
	input.seekg(frameOffset, ios_base::beg);
	model->frames = new MD2Frame[numFrames];
	model->numFrames = numFrames;
	for(int i = 0; i < numFrames; i++)
	{
		MD2Frame* frame = model->frames + i;
		frame->vertices = new MD2Vertex[numVertices];
		Vector scale = readVec3f(input);
		Vector translation = readVec3f(input);
		input.read(frame->name, 16);
		//print all frame names
		//printf("%s\n", frame->name);
		
		for(int j = 0; j < numVertices; j++)
		{
			MD2Vertex* vertex = frame->vertices + j;
			input.read(buffer, 3);
			Vector v((unsigned char)buffer[0],
					(unsigned char)buffer[1],
					(unsigned char)buffer[2]);
			vertex->pos = translation + Vector(scale[0] * v[0],
											  scale[1] * v[1],
											  scale[2] * v[2]);
			input.read(buffer, 1);
			int normalIndex = (int)((unsigned char)buffer[0]);
			vertex->normal = Vector(NORMALS[3 * normalIndex],
								   NORMALS[3 * normalIndex + 1],
								   NORMALS[3 * normalIndex + 2]);
		}
	}
	
	model->startFrame = 0;
	model->endFrame = numFrames - 1;
	return model;
}

void MD2Model::setAnimation( char* name, char *frameToReturn, bool _returnToPreviousFrame)
{
	if (_returnToPreviousFrame)
	{
		returnToPreviousFrame = true;
		returnFrame = frameToReturn;
	}

	//The names of frames normally begin with the name of the animation in
	//which they are, e.g. "run", and are followed by a non-alphabetical
	//character.  Normally, they indicate their frame number in the animation,
	//e.g. "run_1", "run_2", etc.
	bool found = false;
	for(int i = 0; i < numFrames; i++)
	{
		MD2Frame* frame = frames + i;
		//printf("strlen(frame->name): %d, strlen(name): %d, strncmp(frame->name, name, strlen(name)): %d, isalpha(frame->name[strlen(name)]): %d", strlen(frame->name), strlen(name), strncmp(frame->name, name, strlen(name)), isalpha(frame->name[strlen(name)]));
		if (/*(strlen(frame->name) > strlen(name) &&*/
			strncmp(frame->name, name, strlen(name)) == 0 /*&&
			!isalpha(frame->name[strlen(name)])*/)
		{
			if (!found)
			{
				found = true;
				startFrame = i;
			}
			else
			{
				endFrame = i;
			}
		}
		else if (found)
		{
			break;
		}
	}

	time = 0;
}

void MD2Model::advance(float dt)
{
	if (isDead)
		return;

	if (dt < 0)
	{
		return;
	}

	time += dt;
	if (time < 1000000000)
	{
		time -= (int)time;
	}
	else
	{
		time = 0;
	}
}

void MD2Model::draw()
{
	//if (time == 0 && startFrame != 0)
	//	printf("startFrame: %d, endFrame: %d\n", startFrame, endFrame);

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, textureId);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	
	//Figure out the two frames between which we are interpolating
	int frameIndex1 = (int)(time * (endFrame - startFrame + 1)) + startFrame;
	//printf("frameIndex1: %d\n", frameIndex1);
	if (frameIndex1 > endFrame)
	{
		frameIndex1 = startFrame;
	}
	
	int frameIndex2;
	if (frameIndex1 < endFrame)
	{
		frameIndex2 = frameIndex1 + 1;
	}
	else
	{
		frameIndex2 = startFrame;
	}

	//when player dies and we get to the last frame - player is dead
	if (frameIndex2 == startFrame)
	{
		if (dies)
			isDead = true;

		if (returnToPreviousFrame && !isDead)
		{
			returnToPreviousFrame = false;
			setAnimation(returnFrame);
			frameIndex1 = startFrame;
			frameIndex2 = frameIndex1 + 1;
			time = 0;
		}

		finishedAnimation = true;
	}
	
	MD2Frame* frame1 = frames + frameIndex1;
	MD2Frame* frame2 = frames + frameIndex2;
	
	//Figure out the fraction that we are between the two frames
	float frac =
		(time - (float)(frameIndex1 - startFrame) /
		 (float)(endFrame - startFrame + 1)) * (endFrame - startFrame + 1);
	
	trianglesToCollide = new Vector[numTriangles * 3];
	//Draw the model as an interpolation between the two frames
	glBegin(GL_TRIANGLES);
	for(int i = 0; i < numTriangles; i++)
	{
		MD2Triangle* triangle = triangles + i;
		for(int j = 2; j >= 0; j--)
		{
			MD2Vertex* v1 = frame1->vertices + triangle->vertices[j];
			MD2Vertex* v2 = frame2->vertices + triangle->vertices[j];
			Vector pos = v1->pos * (1 - frac) + v2->pos * frac;
			Vector normal = v1->normal * (1 - frac) + v2->normal * frac;
			if (normal[0] == 0 && normal[1] == 0 && normal[2] == 0)
				normal = Vector(0, 0, 1);
			
			glNormal3f(normal[0], normal[1], normal[2]);
			
			MD2TexCoord* texCoord = texCoords + triangle->texCoords[j];
			glTexCoord2f(texCoord->texCoordX, texCoord->texCoordY);
			glVertex3f(pos[0], pos[1], pos[2]);

			trianglesToCollide[i * 3 + j] = Vector(pos[0], pos[1], pos[2]);
		}
	}
	glEnd();
	glDisable(GL_TEXTURE_2D);
}

MD2Triangle* MD2Model::getTriangles()
{
	return triangles;
}
int MD2Model::getNumTriangles()
{
	return numTriangles;
}

void MD2Model::hit()
{
	if (life == 0)
	{
		life = 3;
		setAnimation("run");
		dies = false;
		isDead = false;
		return;
	}

	if (life > 0)
		--life;

	if (life > 0)
	{
		short i = rand() % 3;
		if (i == 0)
			setAnimation("pain1", "run", true);
		else if (i == 1)
			setAnimation("pain2", "run", true);
		else
			setAnimation("pain3", "run", true);
	}

	if (life == 0)
	{
		setAnimation("death2");
		dies = true;
	}
}

bool MD2Model::getFinishedAnimation()
{
	return finishedAnimation;
}

void MD2Model::setFinishedAnimation(bool value)
{
	finishedAnimation = value;
}